#include <signal.h>

#include <linux/config.h>
#include <linux/head.h>
#include <linux/kernel.h>
#include <asm/system.h>

int do_exit(long code);

#define invalidate() \
__asm__("movl %%eax,%%cr3"::"a" (0))

#if (BUFFER_END < 0x100000)
#define LOW_MEM 0x100000
#else
#define LOW_MEM BUFFER_END
#endif

/* these are not to be changed - thay are calculated from the above */
#define PAGING_MEMORY (HIGH_MEMORY - LOW_MEM)
#define PAGING_PAGES (PAGING_MEMORY/4096)
#define MAP_NR(addr) (((addr)-LOW_MEM)>>12)

#if (PAGING_PAGES < 10)
#error "Won't work"
#endif

#define copy_page(from,to) \
__asm__("cld ; rep ; movsl"::"S" (from),"D" (to),"c" (1024):"cx","di","si")

static unsigned short mem_map [ PAGING_PAGES ] = {0,};

// /*
//  * Get physical address of first (actually last :-) free page, and mark it
//  * used. If no free pages left, return 0.
//  */
// unsigned long get_free_page(void)
// {
// register unsigned long __res asm("ax");

// __asm__("std ; repne ; scasw\n\t"
// 	"jne 1f\n\t"
// 	"movw $1,2(%%edi)\n\t"// $1 立即数1； %%edi 同%dei
// 	"sall $12,%%ecx\n\t"
// 	"movl %%ecx,%%edx\n\t"
// 	"addl %2,%%edx\n\t"
// 	"movl $1024,%%ecx\n\t"
// 	"leal 4092(%%edx),%%edi\n\t"
// 	"rep ; stosl\n\t"
// 	"movl %%edx,%%eax\n"
// 	"1:"
// 	:"=a" (__res)
// 	:"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES),"D" (mem_map+PAGING_PAGES-1)
// 	:"di","cx","dx");
// return __res;
// }

unsigned long get_free_page(void)
{
    register unsigned long __res asm("ax"); // 将返回值绑定到 eax 寄存器

    __asm__  (
        "std ; repne ; scasw\n\t" // 设置方向标志位（DF），使字符串操作从高地址向低地址进行，并重复扫描字直到找到不匹配项
        // 比较ax==%dx
	"jne 1f\n\t"             // 如果没有找到空闲页面，跳转到标签 1 处理失败情况
        "movw $1,2(%%edi)\n\t"   // 将立即数 1 写入 mem_map 中当前页面的标志位，表示页面已被使用
        "sall $12,%%ecx\n\t"     // 将 ecx 左移 12 位，计算页面的起始地址（4096 字节对齐）// ecx 相当于 mem_map索引
        "movl %%ecx,%%edx\n\t"   // 将 ecx 的值复制到 edx，作为页面起始地址
        "addl %2,%%edx\n\t"      // 将 PAGING_PAGES 加到 edx 上，得到实际的物理地址
        "movl $1024,%%ecx\n\t"   // 设置 ecx 为 1024，准备填充页面（1024 次循环）
        "leal 4092(%%edx),%%edi\n\t" // 将 edx + 4092 的地址加载到 edi，准备填充页面末尾
        "rep ; stosl\n\t"        // 使用 stosl 指令将 0 填充整个页面（4096 字节）
        "movl %%edx,%%eax\n"     // 将最终的物理地址存储到 eax
        "1:"                     // 标签 1，用于跳转点

        : "=a" (__res)           // 输出操作数：结果存储在 eax 中
        : "0" (0),               // 输入操作数：将立即数 0 放入 eax
          "i" (LOW_MEM),         // 输入操作数：LOW_MEM 是一个立即数常量，表示低内存起始地址
          "c" (PAGING_PAGES),    // 输入操作数：将 PAGING_PAGES 绑定到 ecx //相当于基址
          "D" (mem_map+PAGING_PAGES-1) // 输入操作数：将 mem_map + PAGING_PAGES - 1 加载到 edi
        : "di", "cx", "dx"       // 破坏的寄存器：edi、ecx 和 edx 在汇编代码执行后可能被改变
    );

    return __res; // 返回分配页面的物理地址，如果没有可用页面则返回 0
}



/*
 * Free a page of memory at physical address 'addr'. Used by
 * 'free_page_tables()'
 */
void free_page(unsigned long addr)
{
	if (addr<LOW_MEM) return;
	if (addr>HIGH_MEMORY)
		panic("trying to free nonexistent page");
	addr -= LOW_MEM; // 绝对地址转相对地址
	addr >>= 12; // 相对地址转页号？
	if (mem_map[addr]--) return;
	mem_map[addr]=0;
	panic("trying to free free page");
}

/*
 * This function frees a continuos block of page tables, as needed
 * by 'exit()'. As does copy_page_tables(), this handles only 4Mb blocks.
 */
int free_page_tables(unsigned long from,unsigned long size)
{
	unsigned long *pg_table;
	unsigned long * dir, nr;

	if (from & 0x3fffff)
		panic("free_page_tables called with wrong alignment");
	if (!from)
		panic("Trying to free up swapper memory space");
	size = (size + 0x3fffff) >> 22;// 段大小 转 页个数
	dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */// 段基址（高10位）转为 页表目录某一项？ 
	for ( ; size-->0 ; dir++) {
		if (!(1 & *dir))
			continue;
		pg_table = (unsigned long *) (0xfffff000 & *dir);// 指向一个页表
		for (nr=0 ; nr<1024 ; nr++) {
			if (1 & *pg_table)
				free_page(0xfffff000 & *pg_table);// 清空一个页
			*pg_table = 0; // 清空页表的一项
			pg_table++;
		}
		free_page(0xfffff000 & *dir);
		*dir = 0; // 清空页表目录的一项
	}
	invalidate();
	return 0;
}

/*
 *  Well, here is one of the most complicated functions in mm. It
 * copies a range of linerar addresses by copying only the pages.
 * Let's hope this is bug-free, 'cause this one I don't want to debug :-)
 *
 * Note! We don't copy just any chunks of memory - addresses have to
 * be divisible by 4Mb (one page-directory entry), as this makes the
 * function easier. It's used only by fork anyway.
 *
 * NOTE 2!! When from==0 we are copying kernel space for the first
 * fork(). Then we DONT want to copy a full page-directory entry, as
 * that would lead to some serious memory waste - we just copy the
 * first 160 pages - 640kB. Even that is more than we need, but it
 * doesn't take any more memory - we don't copy-on-write in the low
 * 1 Mb-range, so the pages can be shared with the kernel. Thus the
 * special case for nr=xxxx.
 */
int copy_page_tables(unsigned long from,unsigned long to,long size)
{
	unsigned long * from_page_table;
	unsigned long * to_page_table;
	unsigned long this_page;
	unsigned long * from_dir, * to_dir;
	unsigned long nr;

	if ((from&0x3fffff) || (to&0x3fffff))
		panic("copy_page_tables called with wrong alignment");
	from_dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */
	to_dir = (unsigned long *) ((to>>20) & 0xffc);
	size = ((unsigned) (size+0x3fffff)) >> 22;
	for( ; size-->0 ; from_dir++,to_dir++) {
		if (1 & *to_dir)
			panic("copy_page_tables: already exist");
		if (!(1 & *from_dir))
			continue;
		from_page_table = (unsigned long *) (0xfffff000 & *from_dir);
		if (!(to_page_table = (unsigned long *) get_free_page()))
			return -1;	/* Out of memory, see freeing */
		*to_dir = ((unsigned long) to_page_table) | 7;
		nr = (from==0)?0xA0:1024;
		for ( ; nr-- > 0 ; from_page_table++,to_page_table++) {
			this_page = *from_page_table;
			if (!(1 & this_page))
				continue;
			this_page &= ~2;
			*to_page_table = this_page;
			if (this_page > LOW_MEM) {
				*from_page_table = this_page;
				this_page -= LOW_MEM;
				this_page >>= 12;
				mem_map[this_page]++;
			}
		}
	}
	invalidate();
	return 0;
}

// /*
//  * This function puts a page in memory at the wanted address.
//  * It returns the physical address of the page gotten, 0 if
//  * out of memory (either when trying to access page-table or
//  * page.)
//  */
// unsigned long put_page(unsigned long page,unsigned long address)
// {
// 	unsigned long tmp, *page_table;

// /* NOTE !!! This uses the fact that _pg_dir=0 */

// 	if (page < LOW_MEM || page > HIGH_MEMORY)
// 		printk("Trying to put page %p at %p\n",page,address);
// 	if (mem_map[(page-LOW_MEM)>>12] != 1)
// 		printk("mem_map disagrees with %p at %p\n",page,address);
// 	page_table = (unsigned long *) ((address>>20) & 0xffc);
// 	if ((*page_table)&1)
// 		page_table = (unsigned long *) (0xfffff000 & *page_table);
// 	else {
// 		if (!(tmp=get_free_page()))
// 			return 0;
// 		*page_table = tmp|7;
// 		page_table = (unsigned long *) tmp;
// 	}
// 	page_table[(address>>12) & 0x3ff] = page | 7;
// 	return page;
// }


/*
 * put_page - 将一个页面放置在指定的虚拟地址处，并返回该页面的物理地址。
 * 如果内存不足（例如，在尝试访问页表或页面时），则返回0。
 *
 * 参数：
 *   unsigned long page - 要放置的页面的物理地址。
 *   unsigned long address - 期望映射到的虚拟地址。
 *
 * 返回值：
 *   成功时返回页面的物理地址；失败时返回0。
 */
unsigned long put_page(unsigned long page, unsigned long address)
{
	// 定义临时变量tmp用于存储新分配的页表页面的地址
	// 和page_table指针用于指向当前处理的页表。
	unsigned long tmp, *page_table;

	/* 注意：此函数依赖于全局变量_pg_dir等于0的事实 */

	// 检查提供的页面是否位于合法的内存范围内（LOW_MEM和HIGH_MEMORY之间）。
	// 如果不在范围内，打印警告信息。
	if (page < LOW_MEM || page > HIGH_MEMORY)
		printk("Trying to put page %p at %p\n", (void *)page, (void *)address);

	// 检查mem_map数组中对应位置的值是否为1，
	// 这是为了确保页面的状态与预期一致（即页面是可用的）。
	// 如果不一致，打印警告信息。
	if (mem_map[(page-LOW_MEM)>>12] != 1) // page物理地址？
		printk("mem_map disagrees with %p at %p\n", (void *)page, (void *)address);

	// 计算并设置page_table指针指向对应的页目录项。
	// 这里通过将address右移20位来获取页目录索引，
	// 然后使用掩码0xffc获得页目录项的低12位地址（假设页目录基址为0）。
	page_table = (unsigned long *) ((address>>20) & 0xffc); // 段基址高10位作为（页表目录一项）页表地址？

	// 检查页目录项是否已经被设置（最低位为1表示有效）。
	// 如果已被设置，则清除标志位以得到真实的页表物理地址。
	if ((*page_table)&1)
		page_table = (unsigned long *) (0xfffff000 & *page_table);
	else {
		// 如果页表不存在，则尝试从系统中分配一个新的空闲页面作为页表。
		if (!(tmp=get_free_page()))
			return 0; // 如果无法分配新的页面，则返回0表示失败。

		// 更新页目录项以指向新的页表，并设置权限标志7（用户/读写/可执行）。
		*page_table = tmp | 7;
		page_table = (unsigned long *) tmp; // 设置page_table指针指向新分配的页表。？？
	}

	// 计算页表中的具体条目索引，并设置其值为提供的page加上权限标志7。
	// 这里的计算是基于虚拟地址的低12位被用作页内偏移，因此需要右移12位。
	// 掩码0x3ff用来限制索引在页表的有效范围内（0-1023）。
	page_table[(address>>12) & 0x3ff] = page | 7; //段基址 第10位到第20位（左右）作为页号？

	// 返回提供的page，即物理页面的地址。
	return page;
}




void un_wp_page(unsigned long * table_entry)
{
	unsigned long old_page,new_page;

	old_page = 0xfffff000 & *table_entry;
	if (old_page >= LOW_MEM && mem_map[MAP_NR(old_page)]==1) {
		*table_entry |= 2;
		return;
	}
	if (!(new_page=get_free_page()))
		do_exit(SIGSEGV);
	if (old_page >= LOW_MEM)
		mem_map[MAP_NR(old_page)]--;
	*table_entry = new_page | 7;
	copy_page(old_page,new_page);
}	

/*
 * This routine handles present pages, when users try to write
 * to a shared page. It is done by copying the page to a new address
 * and decrementing the shared-page counter for the old page.
 */
void do_wp_page(unsigned long error_code,unsigned long address)
{
	un_wp_page((unsigned long *)
		(((address>>10) & 0xffc) + (0xfffff000 &
		*((unsigned long *) ((address>>20) &0xffc)))));

}

void write_verify(unsigned long address)
{
	unsigned long page;

	if (!( (page = *((unsigned long *) ((address>>20) & 0xffc)) )&1))
		return;
	page &= 0xfffff000;
	page += ((address>>10) & 0xffc);
	if ((3 & *(unsigned long *) page) == 1)  /* non-writeable, present */
		un_wp_page((unsigned long *) page);
	return;
}

void do_no_page(unsigned long error_code,unsigned long address)
{
	unsigned long tmp;

	if (tmp=get_free_page())
		if (put_page(tmp,address))
			return;
	do_exit(SIGSEGV);
}

void calc_mem(void)
{
	int i,j,k,free=0;
	long * pg_tbl;

	for(i=0 ; i<PAGING_PAGES ; i++)
		if (!mem_map[i]) free++;
	printk("%d pages free (of %d)\n\r",free,PAGING_PAGES);
	for(i=2 ; i<1024 ; i++) {
		if (1&pg_dir[i]) {
			pg_tbl=(long *) (0xfffff000 & pg_dir[i]);
			for(j=k=0 ; j<1024 ; j++)
				if (pg_tbl[j]&1)
					k++;
			printk("Pg-dir[%d] uses %d pages\n",i,k);
		}
	}
}
